﻿using System;
using System.Collections.Generic;
using System.Linq;
using MathNet.Numerics.LinearAlgebra;

using cit.CopyDetection.DatabaseAccess;
using cit.CopyDetection.DataObject;

namespace cit.CopyDetection.Model
{
    /// <summary>
    /// Mô hình không gian vector cho giải thuật LSI
    /// </summary>
    public class VectorSpace
    {
        // Ma trận vector riêng trái
        private Matrix _leftSingularVectors;
        // Vector giá trị riêng
        private Vector _singularValues;
        // Ma trận vector riêng phải
        private Matrix _rightSingularVectors;
        // Số chiều không gian ngữ nghĩa
        private int _semanticDimension = -1;
        // Phần trăm số Frobenius thay đổi
        private double _frobeniusChangePercent = -1D;
        // Ma trận vector riêng phải k chiều
        private Matrix _Vk;
        // Đối tượng thực hiện mô hình hóa tài liệu kiểm tra
        private ModelCreator _modelCreator;
        // Bảng các mức độ tương tự
        private double[][] _similarities;
        // _temp = _Uk * _Ek.Inverse()
        private Matrix _temp;
        // _VkNorms[i] = _Vk.GetRowVector(i).Norm()
        private double[] _VkNorms;
        // Số lượng các đoạn văn bản
        private int _nparagraphs;
        // Vị trí các tài liệu trong mô hình 
        private Dictionary<Document, Position> _positions;

        private string _modelPath = ConfigReader.ModelPath;
        private string _vocabularyPath = ConfigReader.VocabularyPath;
        private double _alpha = ConfigReader.Threshold;

        /// <summary>
        /// Khởi tạo không gian vector
        /// </summary>
        public VectorSpace() 
        {
            LoadModel();
        }

        /// <summary>
        /// Số chiều không gian ngữ nghĩa
        /// </summary>
        public int SemanticDimension
        {
            get { return _semanticDimension; }

            set
            {
                if (_singularValues == null)
                    throw new ApplicationException("Model hasn't loaded yet");

                if (_semanticDimension != value)
                {
                    _semanticDimension = value;
                    _frobeniusChangePercent = GetFrobeniusChangePercent(_semanticDimension);
                    TruncateMatrix(_semanticDimension);
                }
            }
        }

        /// <summary>
        /// Phần trăm số Frobenius thay đổi
        /// </summary>
        public double FrobeniusChangePercent
        {
            get { return _frobeniusChangePercent; }

            set
            {
                if (_singularValues == null)
                    throw new ApplicationException("Model hasn't loaded yet");

                if (_frobeniusChangePercent != value)
                {
                    _frobeniusChangePercent = value;
                    _semanticDimension = GetSemanticDimension(_frobeniusChangePercent);
                    TruncateMatrix(_semanticDimension);
                }
            }
        }

        /// <summary>
        /// Cắt các ma trận theo số chiều không gian ngữ nghĩa
        /// </summary>
        /// <param name="semanticDimension">Số chiều không gian ngữ nghĩa cần cắt</param>
        private void TruncateMatrix(int semanticDimension)
        {
            Matrix Uk = _leftSingularVectors.TruncateColumns(semanticDimension);
            Matrix Ek = Matrix.Diagonal(_singularValues).Truncate(semanticDimension);
            _temp = Uk * Ek.Inverse();

            _Vk = _rightSingularVectors.TruncateColumns(semanticDimension);            
            int rows = _Vk.RowCount;
            _VkNorms = new double[rows];
            for (int i = 0; i < rows; i++)
            {
                _VkNorms[i] = _Vk.GetRowVector(i).Norm();
            } 
        }
        
        /// <summary>
        /// Tải mô hình vector từ file XML
        /// </summary>
        private void LoadModel()
        { 
            string localWtFunc, globalWtFunc;
            _positions = ModelReader.ReadModel(_modelPath, out localWtFunc, out globalWtFunc, out _leftSingularVectors, out _rightSingularVectors, out _singularValues);

            _nparagraphs = _rightSingularVectors.ColumnCount; 

            _modelCreator = new ModelCreator();
            _modelCreator.LocalWtFunc = TermWtFuncFactory.GetIntance().CreateLocalWtFunc(localWtFunc);
            _modelCreator.GlobalWtFunc = TermWtFuncFactory.GetIntance().CreateGlobalWtFunc(globalWtFunc);
            _modelCreator.Vocabulary = ModelReader.ReadVocabulary(_vocabularyPath);
        }

        /// <summary>
        /// Tính số chiểu không gian ngữ nghĩa theo phần trăm số Frobenius thay đổi
        /// </summary>
        /// <param name="percent">Phần trăm số Frobenius thay đổi</param>
        /// <returns>Số chiều không gian ngữ nghĩa ứng với phần trăm số Frobenius thay đổi</returns>
        private int GetSemanticDimension(double percent) 
        {
            int rank = _singularValues.Length;
            double frobenius = _singularValues.Norm();
            double p1 = 0.0D, p2 = 0.0D;
            int k;
            for (k = rank - 1; k > 0; k--)
            {
                double delta = 0.0D;
                for (int i = rank - 1; i >= k; i--)
                {
                    delta += _singularValues[i] * _singularValues[i];
                }
                p2 = Math.Sqrt(delta) / frobenius;
                if (p2 >= percent) break;
                p1 = p2;
            }
            if (k == 0 || k == 1) return 1;            
            if ((p2 - percent) > (percent - p1)) return (k + 1);
            return k;
        }

        /// <summary>
        /// Tính phần trăm số Frobenius thay đổi theo số chiều không gian ngữ nghĩa
        /// </summary>
        /// <param name="semanticDimension">Số chiều không gian ngữ nghĩa</param>
        /// <returns>Phần trăm số Frobenius thay đổi theo số chiều không gian ngữ nghĩa</returns>
        private double GetFrobeniusChangePercent(int semanticDimension)
        {
            double delta = 0;
            int rank = _singularValues.Length;
            double frobenius = _singularValues.Norm();
            for (int i = rank - 1; i >= semanticDimension; i--)
            {
                double value = _singularValues[i];
                delta += value * value;
            }

            return Math.Sqrt(delta) / frobenius;
        }

        /// <summary>
        /// Tính các mức độ tương tự giữa các đoạn văn bản
        /// </summary>
        /// <param name="verifiedDocument">Tập các đoạn văn bản của tài liệu kiểm tra</param>
        private void ComputeSimilarities(List<ParagraphModel> verifiedDocument)
        {
            int size = verifiedDocument.Count;
            _similarities = new double[size][];           
            for (int i = 0; i < size; i++)
            {
                _similarities[i] = new double[_nparagraphs];
                Vector pk = (verifiedDocument[i].Vector.ToRowMatrix() * _temp).GetRowVector(0);
                for (int j = 0; j < _nparagraphs; j++)
                {
                    double denominator = pk.Norm() * _VkNorms[j];
                    if (denominator != 0)
                        _similarities[i][j] = Math.Abs((pk * _Vk.GetRowVector(j)) / denominator);
                }
            }
        }

        /// <summary>
        /// Lấy về độ tương ứng giữa tài liệu kiểm tra với một tài liệu đã đăng ký xác định bởi
        /// chỉ số cột bắt đầu và chỉ số cột kết thúc trong ma trận thuật ngữ x tài liệu
        /// </summary>
        /// <param name="verifiedDocument">Tài liệu kiểm tra</param>
        /// <param name="begin">Chỉ số cột bắt đầu ứng với tài liệu đăng ký</param>
        /// <param name="end">Chỉ số cột kết thúc ứng với tài liệu đăng ký</param>
        /// <returns>Mức độ tương tự giữa hai tài liệu</returns>
        private double GetSimilarity(List<ParagraphModel> verifiedDocument, int begin, int end)
        {
            int size = verifiedDocument.Count;
            double sim = 0D;

            int docLength = verifiedDocument.Sum(p => p.ParagraphInfo.Length);
            for (int i = 0; i < size; i++)
            {
                double max = GetMaxValue(i, _alpha, begin, end);
                int paraLength = verifiedDocument[i].ParagraphInfo.Length;
                sim += ((double)paraLength / docLength) * max;
            }

            return sim;
        }

        /// <summary>
        /// Lấy giá trị lớn nhất trong một khoảng với ngưỡng cho trước
        /// </summary>
        /// <param name="position">Vị trí đoạn văn bản</param>
        /// <param name="threshold">Ngưỡng giới hạn</param>
        /// <param name="begin">Chỉ số đầu tiên</param>
        /// <param name="end">Chỉ số cuối cùng</param>
        /// <returns>Giá trị lớn nhất trong ngưỡng đã cho</returns>
        private double GetMaxValue(int position, double threshold, int begin, int end)  
        {
            double max = double.MinValue;
            for (int i = begin; i <= end; i++)
            {
                double temp = _similarities[position][i];
                if (temp > max) max = temp;
            }

            if (max < threshold) max = 0D;

            return max;
        }

        /// <summary>
        /// Lấy về các tài liệu và mức độ tương tự ngữ nghĩa tương ứng với tài liệu kiểm tra
        /// </summary>
        /// <param name="file">Đường dẫn của tài liệu cần kiểm tra</param>
        /// <returns>Các tài liệu cùng với mức độ tương tự tương ứng</returns>
        public Dictionary<Document, double> GetSimilarDocuments(string file) 
        {
            //1. Mô hình hoá tài liệu
            List<ParagraphModel> paraModels = _modelCreator.ModelVerifiedDocument(file);

            if (_temp == null || _Vk == null || _VkNorms == null)
                throw new ApplicationException("The semantic dimension hasn't set yet"); 

            //2. Tính mức độ tượng tự giữa các đoạn văn bản
            ComputeSimilarities(paraModels);

            //3. Danh sách các tài liệu cùng với mức độ tương tự tương ứng
            Dictionary<Document, double> similarDocuments = new Dictionary<Document, double>();

            //3.1 Mức độ tương tự giữa tài liệu và thư viện
            Document library = new Document("Library", ConfigReader.DocumentFolder);
            similarDocuments.Add(library, GetSimilarity(paraModels, 0, _nparagraphs - 1));

            //3.2 Mức độ tương tự giữa các tài liệu
            foreach (KeyValuePair<Document, Position> position in _positions)
            {
                Document document = position.Key;
                Position pos = position.Value;

                double sim = GetSimilarity(paraModels, pos.Begin, pos.End);
                similarDocuments.Add(document, sim);
            }

            return similarDocuments;
        }
    }
}
